namespace Wilco.CodeHilighter.Engine.SyntaxHighlighting
{
    using System;
    using System.Drawing;
    using System.Linq;
    using System.Xml.Linq;

    /// <summary>
    /// Represents a string line scanner.
    /// </summary>
    public class StringLineScanner : ScannerBase
    {
        #region Fields

        private StringNode stringNode;

        #endregion Fields

        #region Constructors

        /// <summary>
        /// Initializes a new instance of a <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.StringLineScanner"/> class.
        /// </summary>
        public StringLineScanner()
            : this(null, null, null)
        {
            //
        }

        /// <summary>
        /// Initializes a new instance of a <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.StringLineScanner"/> class.
        /// </summary>
        /// <param name="tokenizer">The <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.TokenizerBase"/> which is used to tokenize the source code.</param>
        /// <param name="scannerResult">The <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.OccurrenceCollection"/> which will contain the scanner result.</param>
        public StringLineScanner(TokenizerBase tokenizer, OccurrenceCollection scannerResult)
            : this(tokenizer, scannerResult, new StringNode())
        {
            //
        }

        /// <summary>
        /// Initializes a new instance of a <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.StringLineScanner"/> class.
        /// </summary>
        /// <param name="tokenizer">The <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.TokenizerBase"/> which is used to tokenize the source code.</param>
        /// <param name="scannerResult">The <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.OccurrenceCollection"/> which will contain the scanner result.</param>
        /// <param name="stringNode">A <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.StringNode"/> object.</param>
        public StringLineScanner(TokenizerBase tokenizer, OccurrenceCollection scannerResult, StringNode stringNode)
            : base(tokenizer, scannerResult)
        {
            this.ScannerResult = scannerResult;
            this.stringNode = stringNode;
            this.SetID("StringLineScanner");
        }

        #endregion Constructors

        #region Properties

        /// <summary>
        /// Gets or sets the string node.
        /// </summary>
        public StringNode StringNode
        {
            get
            {
                return this.stringNode;
            }
            set
            {
                if (value != this.stringNode)
                {
                    this.stringNode = value;
                }
            }
        }

        #endregion Properties

        #region Methods

        /// <summary>
        /// Initializes a new instance of a <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.IScanner"/> implementation class.
        /// </summary>
        /// <param name="tokenizer">The <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.TokenizerBase"/> which is used to tokenize the source code.</param>
        /// <param name="scannerResult">The <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.OccurrenceCollection"/> which will contain the scanner result.</param>
        /// <returns>A new instance of a <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.IScanner"/> implementation class.</returns>
        public override IScanner Create(TokenizerBase tokenizer, OccurrenceCollection scannerResult)
        {
            return new StringLineScanner(tokenizer, scannerResult);
        }

        /// <summary>
        /// Loads the state of the scanner.
        /// </summary>
        /// <param name="state">An <see cref="System.Object"/> that contains the state of the scanner.</param>
        public override void LoadState(object state)
        {
            XElement element = (XElement)state;

            // Load settings.
            this.stringNode.BackColor = ColorTranslator.FromHtml(Helper.GetAttributeValueFromElement(element, "settings", "setting","name", "BackColor"));
            this.stringNode.ForeColor = ColorTranslator.FromHtml(Helper.GetAttributeValueFromElement(element, "settings", "setting", "name", "ForeColor"));
            this.stringNode.Font = Helper.GetAttributeValueFromElement(element, "settings", "setting", "name", "Font");
            this.stringNode.NavigateUrl = Helper.GetAttributeValueFromElement(element, "settings", "setting", "name", "NavigateUrl");

            // Load entities.
            foreach (XElement entityElement in element.Descendants("entities").Descendants("entity"))
            {
                if (entityElement != null)
                {
                    this.stringNode.Entities.Add(
                        new StringEntity(
                            entityElement.Attribute("start").Value,
                            entityElement.Attribute("end").Value,
                            entityElement.Attribute("escape").Value));
                }
            }
        }

        /// <summary>
        /// Saves the current state of the scanner.
        /// </summary>
        /// <param name="container">The container which will contain the state.</param>
        /// <returns>An <see cref="System.Object"/> that contains the state of the scanner.</returns>
        public override object SaveState(object container)
        {
            //XDocument document = (XDocument)container;
            XElement element = (XElement)base.SaveState(container);

            // Save settings.
            XElement settingRootElement = new XElement("settings");
            element.Add(settingRootElement);

            settingRootElement.Add(this.CreateSetting("BackColor", ColorTranslator.ToHtml(this.stringNode.BackColor)));
            settingRootElement.Add(this.CreateSetting("ForeColor", ColorTranslator.ToHtml(this.stringNode.ForeColor)));
            settingRootElement.Add(this.CreateSetting("Font", this.stringNode.Font));
            settingRootElement.Add(this.CreateSetting("NavigateUrl", this.stringNode.NavigateUrl));

            // Save entities.
            XElement entityRootElement = new XElement("entities");
            element.Add(entityRootElement);
            XElement entityElement;
            foreach (StringEntity entity in this.stringNode.Entities)
            {
                entityElement = new XElement("entity");
                entityElement.SetAttributeValue("start", entity.Start);
                entityElement.SetAttributeValue("end", entity.End);
                entityElement.SetAttributeValue("escape", entity.Escape);
                entityRootElement.Add(entityElement);
            }

            return element;
        }

        /// <summary>
        /// Scans a token.
        /// </summary>
        /// <remarks>
        /// An <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.IScanner"/> implementation will generally have a reference to a 
        /// <see cref="Wilco.CodeHilighter.Engine.SyntaxHighlighting.NodeCollection"/> which will be used to store results of a scan.
        /// </remarks>
        /// <param name="token">A token from the source code.</param>
        public override void Scan(string token)
        {
            if (!this.Enabled)
            {
                if (this.Child != null)
                {
                    this.Child.Scan(token);
                }
            }
            else
            {
                bool isMatch = false;

                for (int i = 0; i < this.stringNode.Entities.Count; i++)
                {
                    if ((this.Tokenizer.Position + this.stringNode.Entities[i].Start.Length) <= this.Tokenizer.Source.Length)
                    {
                        if (this.Tokenizer.GetNextTokens(this.stringNode.Entities[i].Start.Length) == this.stringNode.Entities[i].Start)
                        {
                            this.Tokenizer.MoveTo(this.Tokenizer.Position + this.StringNode.Entities[i].Start.Length);
                            int startIndex = this.Tokenizer.Position;

                            int endOfLineIndex = this.Tokenizer.Source.IndexOf("\n", this.Tokenizer.Position);
                            if (endOfLineIndex == -1)
                                endOfLineIndex= this.Tokenizer.Source.Length;

                            string escape = ((StringEntity)this.StringNode.Entities[i]).Escape;

                            while (this.Tokenizer.Position <= endOfLineIndex)
                            {
                                if ((this.Tokenizer.Position + escape.Length * 2 <= this.Tokenizer.Source.Length) && this.Tokenizer.GetNextTokens(escape.Length * 2) == (escape + escape))
                                {
                                    this.Tokenizer.MoveTo(this.Tokenizer.Position + escape.Length * 2);
                                }
                                else if ((this.Tokenizer.Position + escape.Length + this.stringNode.Entities[i].End.Length <= this.Tokenizer.Source.Length) &&
                                    this.Tokenizer.GetNextTokens(escape.Length + this.stringNode.Entities[i].End.Length) == (escape + this.stringNode.Entities[i].End))
                                {
                                    this.Tokenizer.MoveTo(this.Tokenizer.Position + escape.Length + this.stringNode.Entities[i].End.Length);
                                }
                                else if ((this.Tokenizer.Position + this.stringNode.Entities[i].End.Length <= this.Tokenizer.Source.Length) &&
                                    this.Tokenizer.GetNextTokens(this.stringNode.Entities[i].End.Length) == this.stringNode.Entities[i].End)
                                {
                                    this.ScannerResult.Add(new Occurrence(startIndex, this.Tokenizer.Position - startIndex, this.stringNode));

                                    this.Tokenizer.MoveTo(this.Tokenizer.Position + this.stringNode.Entities[i].End.Length);
                                    break;
                                }
                                else if (this.Tokenizer.Position == endOfLineIndex)
                                {
                                    if (endOfLineIndex - startIndex > 0)
                                        this.ScannerResult.Add(new Occurrence(startIndex, endOfLineIndex - startIndex, this.stringNode));
                                    break;
                                }
                                else
                                {
                                    this.Tokenizer.MoveTo(this.Tokenizer.Position + 1);
                                }
                            }

                            /*while (this.Tokenizer.Position <= endOfLineIndex)
                            {
                                // e.g.: string x = "hello \\world\\\\";
                                index = this.Tokenizer.Source.IndexOf(this.stringNode.Entities[i].End, this.Tokenizer.Position, endOfLineIndex - this.Tokenizer.Position);
                                if (index > -1)
                                {
                                    if (stringNode.Entities[i].End == escape && index + escape.Length < this.Tokenizer.Source.Length && this.Tokenizer.Source.Substring(index + escape.Length, escape.Length) == escape)
                                    {
                                        this.Tokenizer.MoveTo(index + escape.Length * 2);

                                        // Error.
                                        if (this.Tokenizer.Position == this.Tokenizer.Source.Length)
                                        {
                                            this.ScannerResult.Add(new Occurrence(startIndex, endOfLineIndex - startIndex, this.stringNode));
                                        }
                                    }
                                    else if (stringNode.Entities[i].End != escape && this.Tokenizer.Source.Substring(index - escape.Length, escape.Length) == escape)
                                    {
                                        this.Tokenizer.MoveTo(index + this.stringNode.Entities[i].End.Length);

                                        // Error.
                                        if (this.Tokenizer.Position == this.Tokenizer.Source.Length)
                                        {
                                            this.ScannerResult.Add(new Occurrence(startIndex, endOfLineIndex - startIndex, this.stringNode));
                                        }
                                    }
                                    else
                                    {
                                        // Success.
                                        this.ScannerResult.Add(new Occurrence(startIndex, index - startIndex, this.stringNode));
                                        this.Tokenizer.MoveTo(index + this.stringNode.Entities[i].End.Length);
                                        break;
                                    }
                                }
                                else
                                {
                                    // Error.
                                    this.ScannerResult.Add(new Occurrence(startIndex, endOfLineIndex - startIndex, this.stringNode));
                                    this.Tokenizer.MoveTo(endOfLineIndex - 1);
                                    break;
                                }
                            }*/

                            isMatch = true;
                            break;
                        }
                    }
                }

                if (!isMatch)
                {
                    if (this.Child != null)
                    {
                        this.Child.Scan(token);
                    }
                }
            }
        }

        /// <summary>
        /// Creates a steting.
        /// </summary>
        /// <param name="name">The name of the setting.</param>
        /// <param name="value">The value of the setting.</param>
        /// <returns>The <see cref="System.Xml.XmlElement"/> which represents the setting.</returns>
        private XElement CreateSetting(string name, string value)
        {
            return new XElement("setting", value, new XAttribute("name", name));
        }

        #endregion Methods
    }
}